package com.yesallchina.wxapp.officialaccount.api;

import com.fasterxml.jackson.databind.JsonNode;
import com.yesallchina.wxapp.officialaccount.api.beans.AccessToken;
import com.yesallchina.wxapp.officialaccount.api.beans.ParamName;
import com.yesallchina.wxapp.officialaccount.ext.ApiDataCache;
import com.yesallchina.wxapp.officialaccount.service.WxOfficialAccountInitializer;
import com.yesallchina.wxapp.officialaccount.utils.JacksonUtils;
import com.yesallchina.wxapp.officialaccount.utils.WxHttpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class BasicSupportApi implements InitializingBean {

    private final static Logger log = LoggerFactory.getLogger(BasicSupportApi.class);


    /**
     * 错误码参数名称
     */
    final static String ERRCODE = "errcode";
    final static String ERRMSG = "errmsg";

    /**
     * 微信服务返回码
     */
    final static String RESPONSE_OK = "0";
    /**
     * access_token无效
     */
    final static String ERR_CODE_ACCESS_TOKEN_INVALID = "40001";
    /**
     * access_token不合法
     */
    final static String ERR_CODE_ACCESS_TOKEN_ILLEGAL = "40014";
    /**
     * access_token超时
     */
    final static String ERR_CODE_ACCESS_TOKEN_TIMEOUT = "42001";


    private Lock lock = new ReentrantLock();

    private WxOfficialAccountInitializer wxInit;


    private AccessToken token = null;


    public BasicSupportApi(WxOfficialAccountInitializer wxInit) {
        this.wxInit = wxInit;
    }


    /**
     * 验证微信返回的结果是否成功
     *
     * @param
     * @return
     */
    public static boolean isSuccess(String response) {
        if (!StringUtils.hasText(response)) {
            return false;
        }
        JsonNode node = JacksonUtils.parse(response, JsonNode.class);
        if (node != null
                && (node.get(ERRCODE) == null || RESPONSE_OK.equals(node.get(ERRCODE).asText()))) {
            return true;
        }
        return false;
    }

    public static boolean isError(String response) {
        return !isSuccess(response);
    }

    /**
     * 验证基础access_token是否有效
     *
     * @param
     * @return
     */
    public boolean isInvalidAccessToken(String response) {
        if (!StringUtils.hasText(response)) {
            return false;
        }
        JsonNode node = JacksonUtils.parse(response, JsonNode.class);
        if (node == null) {
            return false;
        }
        String code = node.get(ERRCODE).asText();
        if (!StringUtils.hasText(code)) {
            code = "";
        }
        switch (code) {
            case ERR_CODE_ACCESS_TOKEN_INVALID:
            case ERR_CODE_ACCESS_TOKEN_ILLEGAL:
            case ERR_CODE_ACCESS_TOKEN_TIMEOUT:
                resetToken();
                return true;
            default:
                return false;
        }
    }

    /**
     * access_token是公众号的全局唯一接口调用凭据，公众号调用各接口时都需使用access_token,开发者需要进行妥善保存。
     * <p>
     * access_token的存储至少要保留512个字符空间
     * <p>
     * access_token的有效期目前为2个小时，需定时刷新，重复获取将导致上次获取的access_token失效
     */
    public String getAccessToken() {
        return getToken(0);
    }


    /**
     * callback IP即微信调用开发者服务器所使用的出口IP
     *
     * @return
     */
    public String getCallbackIp() {
        String url = wxInit.getApiProp().getCallbackIp();
        Map<String, String> params = new HashMap<>(1);
        params.put(ParamName.ACCESS_TOKEN, getAccessToken());
        String response = WxHttpUtils.doGet(url, params, wxInit.getCommonProp());
        if (isError(response)) {
            log.error("[获取微信callback IP地址] ： 错误 \r\n  {}", response);
            return null;
        }
        return response;
    }

    /**
     * API接口IP即api.weixin.qq.com的解析地址，由开发者调用微信侧的接入IP
     *
     * @return
     */
    public String getApiDomainIp() {
        String url = wxInit.getApiProp().getApiDomainIp();
        Map<String, String> params = new HashMap<>(1);
        params.put(ParamName.ACCESS_TOKEN, getAccessToken());
        String response = WxHttpUtils.doGet(url, params, wxInit.getCommonProp());
        if (isError(response)) {
            log.error("[ 获取微信API接口 IP地址] ： 错误 \r\n  {}", response);
            return null;
        }
        return response;
    }

    private String getToken(int retry) {
        if (retry > 2) {
            log.error("[微信公众号访问令牌]: 调用获取失败");
            return null;
        }
        String ack = getToken();
        if (StringUtils.hasText(ack)) {
            return ack;
        }
        try {
            lock.lock();
            ack = getToken();
            if (StringUtils.hasText(ack)) {
                return ack;
            }
            resetToken();
            retry++;
            return getToken(retry);
        } finally {
            lock.unlock();
        }
    }

    private String getToken() {
        // 优先从自定义的缓存器中获取
        String tokenCacheClass = wxInit.getSpiProp().getAccessTokenCacheName();
        ApiDataCache<AccessToken> ackCache = null;
        AccessToken ack = this.token;
        if (StringUtils.hasText(tokenCacheClass)) {
            ackCache = wxInit.getAppCtx().getBean(tokenCacheClass);
        }
        if (ackCache != null) {
            ack = ackCache.get();
        }
        if (ack != null && !ack.isDateline()) {
            return ack.getAccessToken();
        }
        return null;
    }


    private void resetToken() {
        if (log.isDebugEnabled()) {
            log.debug("[微信公众号访问令牌]: 初始化...");
        }
        String url = wxInit.getApiProp().getBaseAccessToken();
        final String grantTypeVal = "client_credential";
        Map<String, String> params = new HashMap<>(3);
        params.put(ParamName.GRANT_TYPE, grantTypeVal);
        params.put(ParamName.APP_ID, wxInit.getCommonProp().getAppid());
        params.put(ParamName.SECRET, wxInit.getCommonProp().getAppSecret());
        String response = WxHttpUtils.doGet(url, params, wxInit.getCommonProp());
        JsonNode tokenNode = JacksonUtils.parse(response, JsonNode.class);
        AccessToken mAck = null;
        if (tokenNode != null && tokenNode.get(ParamName.ACCESS_TOKEN) != null) {
            String token = tokenNode.get(ParamName.ACCESS_TOKEN).asText();
            mAck = new AccessToken();
            mAck.setAccessToken(token);
            mAck.setLastDate(System.currentTimeMillis());
            mAck.setAppId(wxInit.getCommonProp().getAppid());
            mAck.setPublicId(wxInit.getCommonProp().getPublicId());
            mAck.setInterval(tokenNode.get(ParamName.EXPIRES_IN).asInt() * 1000);
        }
        // 如果存在额外的缓存处理器
        String tokenCacheClass = wxInit.getSpiProp().getAccessTokenCacheName();
        if (StringUtils.hasText(tokenCacheClass)) {
            ApiDataCache<AccessToken> ackCache = wxInit.getAppCtx().getBean(tokenCacheClass);
            if (ackCache != null && mAck != null) {
                ackCache.save(mAck);
                return;
            }
            return;
        }
        // 否则 缓存在本地
        this.token = mAck;
    }

    public WxOfficialAccountInitializer getWxInit() {
        return wxInit;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (wxInit.getSpiProp().getLoadTokenStartup() == 1){
            resetToken();
        }else{
            log.warn("[微信公众号访问令牌]: 初始化：设置了启动时禁止初始化。0：禁止；1：开启");
        }
    }
}
